#include <retcode.h>
#include <osal/osal.h>
//#include <bus/erom/erom.h>
#include <bus/erom/uart.h>
#include "nim_m3327.h"
#define EROM_SUCCESS		SUCCESS
#define EROM_FAILUE		ERR_FAILUE

#define EROM_CMD_SYNC	   	(0x60)
#define EROM_CMD_SETPC	   	(0x50)
#define EROM_CMD_WM	   	(0x40)		
#define EROM_CMD_RM	   	(0x30)	

#define EROM_RW_BLOCK_LEN_IDX	(15)   	  
#define EROM_RW_BLOCK_LEN  	(2<<EROM_RW_BLOCK_LEN_IDX) 


#define READ_ONLY_IO		(0xB8000000)
#define US_TICKS       (SYS_CPU_CLOCK / 2000000)
#define S_TICKS       (SYS_CPU_CLOCK>>1)

/********************************************************************
	Mini Uart driver to support high speed Uart
*********************************************************************/
#define URBR			0
#define UTBR			0
#define ULSR			5
#define UDDL			0
#define UDML			1
#define UIRE			1
#define UFCR			2
#define ULCR			3
#define UMCR			4
#define UDLM			1
#define UIIR			2

#define M3328F_EROM_BASE_ADDR 0xb8001300

static UINT32 UART1_BASE_ADDR = 0xb8001300;
static UINT32 UART2_BASE_ADDR = 0xb8001600;
static UINT32 UART3_BASE_ADDR = 0xb8001b00;
static UINT32 DEV_RESET_REG = 0xb8000060;

UINT32 uart_base_29e = 0;
UINT8* uart_rst_bits = NULL;

UINT8 uart_rst_bits_29e[4] = {21, 20, 21, 22};
UINT8 uart_rst_bits_3602[4] = {17, 16, 17, 22};



static const unsigned char sync_str[] = "ALi eROM";
static unsigned long cur_baudrate = UART_SPEED_NORMAL ;
extern OSAL_ID nim_28f_reset_flag;

void uart_base_addr_update(UINT32 chip_id)
{
	if(ALI_S3602 == chip_id)
	{
		UART1_BASE_ADDR = 0xb8018300;
		UART2_BASE_ADDR = 0xb8018600;
		UART3_BASE_ADDR = 0xb8018b00;
		DEV_RESET_REG = 0xb8000080;
		uart_rst_bits = uart_rst_bits_3602;
	}
	else if(ALI_M3329E == chip_id)
	{
		UART1_BASE_ADDR = 0xb8001300;
		UART2_BASE_ADDR = 0xb8001600;
		UART3_BASE_ADDR = 0xb8001b00;	
		DEV_RESET_REG = 0xb8000060;
		uart_rst_bits = uart_rst_bits_29e;
	}
}

static inline void uart_hw_reset(UINT8 uart_id)
{
	UINT8 rst_bit;
	
	if(NULL == uart_rst_bits)
	{
		uart_rst_bits = uart_rst_bits_29e;
	}
	
	switch(uart_id)
	{
	case 1:
		uart_base_29e = UART1_BASE_ADDR;
		rst_bit = uart_rst_bits[uart_id];
		break;
	case 2:
		uart_base_29e = UART2_BASE_ADDR;
		rst_bit = uart_rst_bits[uart_id];
		break;
	case 3:
		uart_base_29e = UART3_BASE_ADDR;
		rst_bit = uart_rst_bits[uart_id];
		break;
	default:
		uart_base_29e = UART2_BASE_ADDR;
		rst_bit = uart_rst_bits[2];
	}
	*(unsigned long *)(DEV_RESET_REG) |= 1<<rst_bit;
	osal_delay(1000);
	*(unsigned long *)(DEV_RESET_REG) &= ~(1<<rst_bit);
	osal_delay(1000);
}

static inline void uart_reset(void)
{
	* ( (volatile unsigned char *)(uart_base_29e) + ULCR) = 0x9B;
	* ( (volatile unsigned char *)(uart_base_29e) + UDDL) = 0x01;
	* ( (volatile unsigned char *)(uart_base_29e) + UDLM) = 0x00;
	* ( (volatile unsigned char *)(uart_base_29e) + ULCR) = 0x1b;
	* ( (volatile unsigned char *)(uart_base_29e) + UFCR) = 0x00;
	* ( (volatile unsigned char *)(uart_base_29e) + ULSR) = 0x00;
	* ( (volatile unsigned char *)(uart_base_29e) + UMCR) = 0x03;
	* ( (volatile unsigned char *)(uart_base_29e) + UIRE) = 0x00;
}

static inline void uart_init(UINT8 uart_id)
{
	uart_hw_reset(uart_id);
	uart_reset();
	cur_baudrate = UART_SPEED_NORMAL;
}	
static inline void uart_tx(unsigned char data)
{
	volatile unsigned char status;
	while(1) {
		status = *((volatile unsigned char *)(uart_base_29e) + ULSR);
		if(status & 0x40)
			break;
	}	
	
	*((volatile unsigned char *)(uart_base_29e) + UTBR) = data;
}	
static inline unsigned char uart_rx_p(unsigned char *data)
{
	unsigned char ret = 0xFF;
	volatile unsigned char status;
	
	status = *((volatile unsigned char *)(uart_base_29e) + ULSR);
	if(status & 0x1) {
		*data = *((volatile unsigned char *)(uart_base_29e) + URBR);
		ret = 0;
	}
	
	return ret;	
}

static unsigned char uart_rx_tmo(unsigned char *data, unsigned long us_tmo)
{
	unsigned long tick;
	tick = read_tsc();

	while(1) {
		if(0 == uart_rx_p(data) )
			return 0;

	        if((read_tsc() - tick) >= (US_TICKS*us_tmo))
	        	return 0xFF;
	}
}

static inline unsigned char uart_rx(void)
{
	unsigned char data;
	UINT32 tick = read_tsc();
	while(1)
	{
		if(uart_rx_p(&data)==0)
			break;
		/*read from external demod timeout == 125ms*/
		if((read_tsc()-tick)>=(S_TICKS>>3))
		{
			NIM_PRINTF("RX timeout, cur_task_id: %d\n", osal_task_get_current_id());
			osal_flag_set(nim_28f_reset_flag, NIM_EXT_DEMOD_RESET);
			break;
		}
	}
	return data;
}

/********************************************************************
	eROM API 
*********************************************************************/
static int erom_sync_check(void)
{
	unsigned int i;
	unsigned char ch;
	
	for(i=0;i<sizeof(sync_str);i++){
		ch = sync_str[i];
		nim_28f_ext_erom_wm((M3328F_EROM_BASE_ADDR + UTBR),&ch,1,0);
		if(0!=uart_rx_tmo(&ch,50000)) 
			return EROM_FAILUE;
		if(ch!=sync_str[i])
			return EROM_FAILUE;	
	}

	return EROM_SUCCESS;
}	

/*erom sync*/
int nim_28f_ext_erom_sync(unsigned long sync_tmo, unsigned int mode, UINT8 uart_id)
{
	int ret;
	unsigned char echo;
	unsigned long val32;
	unsigned long tick, us;

	uart_init(uart_id);

	ret = EROM_FAILUE;

	echo = 0;
	sync_tmo = sync_tmo*1000 ;   // in uint of us
	do{
		tick = read_tsc();	
		
		uart_tx(EROM_CMD_SYNC);

		osal_task_sleep(1);

		if(SUCCESS==uart_rx_tmo(&echo,950))
		{
			if(echo == EROM_CMD_SYNC) {
				/*issue WM cmd to prevent eROM timeout*/		
				nim_28f_ext_erom_wm(READ_ONLY_IO,&val32,4,0);
				osal_task_sleep(50);
				ret = erom_sync_check();
				if(EROM_SUCCESS==ret)
					break;
			}	
		}
		
		us = (read_tsc()-tick)/US_TICKS; 
	
		if( sync_tmo < us)
			break;
		
		sync_tmo -= us;	

	} while(1);	 

	osal_task_sleep(100);
	
	return ret;
}	

inline int nim_28f_ext_erom_rw( unsigned long mem_addr, void *buf,unsigned long len,
		 unsigned int block_mode, unsigned int is_read)
{
	unsigned long i, block_len;
	unsigned long cmd_data[2];
		
	while( len>0 )
	{
		/*Eric Li*/
		/*if(0!=block_mode) 
			i = calc_index(len);
		else 
			i = 0;*/
		
		block_len = 1;//<<i ;//Eric Li

		if(0!=is_read)					
			cmd_data[0] = (EROM_CMD_RM)<<24 ;
		else
			cmd_data[0] = (EROM_CMD_WM)<<24 ;
		
		cmd_data[1] = mem_addr;		
	
		// send RM/WM cmd
		for(i=0;i<5;i++) {
			uart_tx(*((unsigned char *)cmd_data+3+i));
		}	
	
		// recv & send data
		for(i=0;i<block_len;i++,buf++) 	{
			if(0!= is_read) {
				*((unsigned char *)buf) = uart_rx();
				osal_delay(1);				
			} else {
				uart_tx(*((unsigned char *)buf));
			}	
		} 	
		
		mem_addr += block_len;
		len -= block_len;
	}	
	
	return EROM_SUCCESS;
}	

/***************************************************************
 #### Note: When switching betweeen high speed (6M and 2M), 
      please switch to normal speed (115200) first.
****************************************************************/
int nim_28f_ext_uart_speed_config(unsigned long baudrate)
{
	unsigned char data8;
	unsigned long data32_ext;
	if( cur_baudrate == baudrate ) {
		return SUCCESS;
	}	

	// switch back to 115200 first if needed
	if( cur_baudrate != UART_SPEED_NORMAL) {
		data8 = 0x00;
		nim_28f_ext_erom_wm(M3328F_EROM_BASE_ADDR+0x08,(void *)(&data8),1,0);		
		osal_task_sleep(100);	
		*((unsigned char *)(uart_base_29e+0x08)) = data8;	
		osal_task_sleep(100);			
	}

	data8 = 0x08;
	switch(baudrate)
	{
		case UART_SPEED_NORMAL:
			cur_baudrate = UART_SPEED_NORMAL ;						
			return SUCCESS ;
			break;
		case UART_HIGH_SPEED_2M:
			data32_ext = 0x40000000;
			cur_baudrate = UART_HIGH_SPEED_2M ;						
			break;
		case UART_HIGH_SPEED_6M:
			data32_ext = 0x40008000;
			cur_baudrate = UART_HIGH_SPEED_6M ;			
			break;		
		default:
			return 	ERR_FAILUE;
	}	

	// config remote
	nim_28f_ext_erom_wm(0xb8000074,(void *)(&data32_ext),4,0);;
	osal_task_sleep(100); // must delay here
	nim_28f_ext_erom_wm(M3328F_EROM_BASE_ADDR+0x08,(void *)(&data8),1,0);		
	osal_task_sleep(100);	

	// config local
	osal_task_sleep(100); // must delay here
	*((unsigned char *)(uart_base_29e+0x08)) = data8;	
	osal_task_sleep(100);
	
	return SUCCESS;	
}

